//  Copyright (c) 2008  Yoong-ki Ahn & Youngjin Kim < mexif@googlegroups.com>
//  See the enclosed file COPYING for license information (MIT Licence).
//  If you did not receive this file,
//  see http://www.opensource.org/licenses/mit-license.php.

package mexif;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

class Marker {
	int marker;
	int length;
	
	int offset;
}

public class MExif extends JpegMarker {
	byte[]	mData = null;
	int		mCurrentPos = 0;
	int		mChunkType=0, mChunkLength=0, mHeaderPosition = 0;
	boolean	mInImage = false;
	
	Vector<Marker> mApps = new Vector<Marker>();
	Exif			mExif = null;
	Jfif			mJfif = null;
	
	public MExif(String jpegFile) throws FileNotFoundException, JpegException, IOException {
		try {
			// Read whole file
			File file = new File(jpegFile);
			mData = new byte[(int) file.length()];
			FileInputStream is = new FileInputStream(file);
			is.read( mData );
			is.close();
		}
		finally {
		}
		scanHeaders();
	}
	
    public MExif(byte[] data) throws JpegException, IOException {
    	mData = data;
		scanHeaders();
    } 
    
    protected void initValues() {
    	mCurrentPos = 0;
    	mChunkType = 0;
    	mChunkLength = 0;
    	mHeaderPosition = 0;
    	mInImage = false;
    }
    
    protected int scanHeaders() throws IOException, JpegException {
    	initValues();
    	
    	// Check First Markers
    	firstMarker();
    	
    	// Initial Chunk type
    	mChunkType = mData[1] & 0xFF;
    	mChunkLength = 0;
    	mHeaderPosition = 0;
    	mCurrentPos = 2;
    	
    	for(int i=0; isValid(); nextMarker(), ++i) {
    		int marker = this.getMarker();
    		int len = this.getLength();
    		//int offset = this.getOffset() - (mChunkLength + 2);
    	
    		int chunk = this.getChunk()-(mChunkLength + 2);

  	
			//  register 'APP marker'
    		if( M_APP0 <= marker && marker <= M_APP15 ){
    			Marker m = new Marker();
    			m.marker = marker;
    			m.length = len-2;
    			m.offset = chunk+2;
    			
    			mApps.add(m);
       		}
    	}
    	
    	fillAppMarkers();
    	
    	return 0;
    }
    
    protected void fillExifInfo(Marker m) throws JpegException {
    	if( mExif != null )
    		throw new JpegException("More than one EXIF Marker");
    	mExif = new Exif(mData, m.offset, m.length);
    }
    
    protected void fillJfifInfo(Marker m) throws JpegException {
    	if( mJfif != null )
    		throw new JpegException("More than one JFIF Marker");
    	mJfif = new Jfif(mData, m.offset, m.length);
    }
    
    protected void fillAppMarkers() throws JpegException{
    	for( Enumeration<Marker> e = mApps.elements(); e.hasMoreElements(); ) {
			Marker m = e.nextElement();
			
			if( m.marker == M_APP0 ) {
				if( ExifUtil.isJfifMarkerPrefixed(mData, m.offset)) {
					fillJfifInfo(m);
				}
			}
			else if( m.marker == M_APP1 ) {
				if( ExifUtil.isExifMarkerPrefixed(mData, m.offset)) {
					fillExifInfo(m);
				}
			}
		}
    }
    
    protected boolean firstMarker() throws IOException, JpegException {
		int c1, c2;
		c1 = readByte();
		c2 = readByte();
		if (c1 != 0xFF || c2 != M_SOI)
		    throw new JpegException("Not a JPEG file");
		return true;
    }
    
    protected boolean nextMarker() {
    	if ( mCurrentPos == 0 )
    		return true;	//	ok. done without error.
    	
    	// out of range
    	if ( mData == null || mCurrentPos < 0 || mCurrentPos >= mData.length )
    		return false;	//	data truncated.
    	
    	if( mChunkType == M_EOI ) {
    		// make it out of range.
    		//pjpeg->next_position = pjpeg->data + pjpeg->length + 1;
    		mCurrentPos = 0;
    		return true;	//	ok. done.
    	}
    	
    	int currentPos = mCurrentPos;
    	if( mInImage ) {
    		boolean marker = false;
    		//	we have to scan.
    		mHeaderPosition = currentPos;
    		mChunkType = M_PseudoImage;
    		mChunkLength = 0;

    		//	skip compressed image data.
    		//	0xff,  0x00 is not marker. keep skipping.
    		//	0xff, !0x00 is marker. stop here.
    		for ( ; ; ++currentPos )
    		{
    			if ( marker )
    			{
    				if ( mData[currentPos] != 0 )
    				{
    					--currentPos;
    					break;
    				}
    				else
    					marker = false;
    			}
    			if ( (mData[currentPos] & 0xFF )== 0xFF )
    				marker = true;
    		}
    		
    		mChunkLength = currentPos - mHeaderPosition;
    		mCurrentPos = currentPos;

    		if ( marker )
    			mInImage = false;
    	}
    	else {
    		if ( (mData[currentPos] & 0xFF) != 0xff )
    			return false;		//	error. data corrupt.

    		mHeaderPosition = currentPos;
    		mChunkType = mData[currentPos+1] & 0xFF;

    		if( M_RST0 <= mChunkType && mChunkType <= M_RST7 ) {
    			mInImage = true;
    			mChunkLength = 0;
    		}
    		else if( mChunkType != M_EOI ) {
    			//pjpeg->chunk_length  = ( pb[2] << 8 ) | pb[3];
    			final int temp1 = mData[currentPos+2] & 0xFF;
    			final int temp2 = mData[currentPos+3] & 0xFF;
    			mChunkLength = (temp1 << 8 ) | temp2;
    		}
    		else
    			mChunkLength = 0;

    		if( mChunkType == M_SOS ) {
    			mInImage = true;
    		}

//    		pjpeg->next_position = pb + pjpeg->chunk_length + 2;
    		mCurrentPos = currentPos + mChunkLength + 2;
    	}

    	//	ok. no error.
    	return true;
    }
    
    int getMarker() {
    	if( isValid() )
    		return mChunkType;
    	else
    		return 0;
    }

    int getLength() {
    	if( isValid() )
    		return mChunkLength;
    	else
    		return 0;
    }
    
    int getChunk() {
    	if( isValid() ) {
    		if( mInImage )
    			return mCurrentPos;
    		else
    			return mCurrentPos+2;
    	}
    	else
    		return 0;
    }
    
    int getOffset() {
    	if( mData == null )
    		return 0;
    	else
    		return mCurrentPos;
    }

    boolean isExif() {
    	if( isValid() ) {
    		if( mChunkType == M_APP1 ) {
    			int chunk = getChunk();

    			chunk += 2;

    			if( ExifUtil.isExifMarkerPrefixed( mData, chunk))
    				return true;
    		}
    	}
    	return false;
    }
    
    protected boolean isValid() {
    	if( mData == null || mCurrentPos < 0 || mCurrentPos >= mData.length )
    		return false;
    	else
    		return true;
    }
    protected int readByte() {
    	int c = mData[mCurrentPos] & 0xFF;
    	mCurrentPos++;
    	return c;
    }
    
    public TagValue[] getExifSection(int section_type) {
    	if( mExif == null )
    		return null;
    	else
    		return mExif.getExifSection(section_type);
    }
//    protected int read32s() {
//    	int c = ByteUtil.get32s(mData, mCurrentPos);
//    	mCurrentPos+=4;
//    	return c;
//    }
    
    
}
